using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Runtime.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Diagnostics;
using System.ComponentModel;

namespace BigNumber
{
  
  public sealed class FactoringManager
  {
    #region Singelton Handling
    private static readonly FactoringManager m_Instance;
    private static object m_SingeltonLock = new object();
    static FactoringManager()
    {
      if (m_Instance == null)
      {
        lock (m_SingeltonLock)
        {
          if (m_Instance == null)
            m_Instance = new FactoringManager();
        }
      }
      
    }

    public static FactoringManager Instance
    {
      get
      {
        return m_Instance;
      }
    }
    #endregion

    [Serializable]
    private class PrimeCalculationData
    {
      private ForewardSkipList<FastBigNumber> m_Primes;
      public ForewardSkipList<FastBigNumber> Primes
      {
        get { return m_Primes; }
        set { m_Primes = value; }
      }
      
      public PrimeCalculationData()
      {
        m_Primes = new ForewardSkipList<FastBigNumber>();
        LastNumberCalculated = 0;
      }

      public FastBigNumber LastNumberCalculated { get; set; }
      public void Serialize(BinaryWriter writer)
      {
        ForewardSkipListNode<FastBigNumber> node = m_Primes.First;
        while (node != null)
        {
          FastBigNumber.Serialize(node.Data, writer);
          node = node.Next;
        }
      }
      public void Deserialize(BinaryReader reader)
      {
        m_Primes = new ForewardSkipList<FastBigNumber>();
        while (true)
        {
          FastBigNumber number = FastBigNumber.Deserialize(reader);
          m_Primes.Add(number);
          LastNumberCalculated = number;
          if (reader.PeekChar() == -1) break;
        }
      }

    }

    private PrimeCalculationData m_Data;
    private string m_PrimesFilename;

    #region Prime Calculation Members
    private Thread m_CalculationThread;
    private object m_CalculationLock = new object();
    private FastBigNumber m_CalculationLimit;
    private const int InitialPrimesMax = 10;
    private BackgroundWorker m_DeserializationWorker;
    #endregion

    #region Factoring Thread Handling Members
    private int THREAD_MAX = 5;
    private int m_BusyThreadCount = 0;
    private Dictionary<BackgroundWorker, bool> m_Threads;
    private LinkedList<WeakReference> m_WaitingNumbers;
    private BackgroundWorker m_WaitingNumbersProcessor;
    #endregion

    #region Prime Calculation and Storage functions
    private void CreateInitialPrimesFile(string fileName)
    {
      m_Data = new PrimeCalculationData();
      Int32 Top = InitialPrimesMax;
      List<int> numbers = new List<int>(Top);
      numbers.Add(1);
      for (int i = 2; i < Top; i++)
        numbers.Add(i);
      // Sieve of Eratosthenes
      for (int i = 1; i < numbers.Count; i++)
      {
        if (numbers[i] != -1)
        {
          int jump = numbers[i];
          m_Data.Primes.Add(new FastBigNumber(jump));
          m_Data.LastNumberCalculated = jump;
          for (int j = i; j < numbers.Count; j += jump)
            numbers[j] = -1;
        }
      }
      SaveToFile(fileName);
    }

    private void CreateBackup(string filename)
    {
      if (File.Exists(filename))
      {
        File.Delete(filename + ".bck");
        File.Copy(filename, filename + ".bck");
      }
    }

    private void SaveToFile(string filename)
    {
      BinaryWriter binWriter = new BinaryWriter(File.Open(filename, FileMode.Create));
      try
      {
        m_Data.Serialize(binWriter);
      }
      finally
      {
        binWriter.Close();
      }

    }

    private void AddToFile(string filename,FastBigNumber number)
    {
      BinaryWriter binWriter = new BinaryWriter(File.Open(filename, FileMode.Append,FileAccess.Write));
      try
      {
        FastBigNumber.Serialize(number,binWriter);
      }
      finally
      {
        binWriter.Close();
      }
    }

    private void LoadFromFile(string filename)
    {
      if (!File.Exists(filename))
        throw new Exception("Filename " + filename + " does not exist in PrimeGenerator LoadFromFile method");

      #region Read file
      bool tryBackup = false;
      using (Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
      {
        BinaryReader reader = new BinaryReader(stream);
        try
        {
          m_Data.Deserialize(reader);
        }

        catch
        {
          tryBackup = true;
        }
        finally
        {
          reader.Close();
        }
      }
#endregion

      if (!tryBackup) return;

      String backupFilename = filename + ".bck";
      if (!File.Exists(backupFilename))
      {
        File.Delete(filename);
        CreateInitialPrimesFile(backupFilename);
      }

      #region Read Backup File
      using (Stream stream = new FileStream(backupFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
      {
        BinaryReader reader = new BinaryReader(stream);
        try
        {
          m_Data.Deserialize(reader);
        }
        catch
        {
          throw new Exception(String.Format("Unable to load primes file or primes file backup {0} or {1}", filename, backupFilename));
        }
        finally
        {
          reader.Close();
        }
      }
      #endregion

      SaveToFile(filename);
    }

    private void InitializePrimesCalculation()
    {
      m_Data = new PrimeCalculationData();
      //m_Data.Primes = new ForewardSkipList<FastBigNumber>();
      m_PrimesFilename = ConfigurationManager.AppSettings["Primes File"];
      m_CalculationThread = new Thread(new ThreadStart(DoCalculation));
      m_CalculationThread.IsBackground = true;
      m_CalculationThread.Priority = ThreadPriority.BelowNormal;

      m_DeserializationWorker = new BackgroundWorker();
      m_DeserializationWorker.DoWork += new DoWorkEventHandler(DeserializationWorker_DoWork);

      if (m_PrimesFilename == null)
      {
        throw new Exception("'Primes File' app setting not defined in application configuration");
      }
      else
      {
        if (!File.Exists(m_PrimesFilename))
        {
          CreateInitialPrimesFile(m_PrimesFilename);
        }
        else
        {
          m_DeserializationWorker.RunWorkerAsync();
          
        }
      }
    }

    void DeserializationWorker_DoWork(object sender, DoWorkEventArgs e)
    {
      LoadFromFile(m_PrimesFilename);
    }
    #endregion

    #region Thread-Pool handling
    private void InitializeThreadPool()
    {
      m_Threads = new Dictionary<BackgroundWorker, bool>();
      m_WaitingNumbers = new LinkedList<WeakReference>();
      for (int i = 0; i < THREAD_MAX; i++)
      {
        BackgroundWorker worker = new BackgroundWorker();
        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        m_Threads.Add(worker, true);
      }
      m_BusyThreadCount = 0;
      m_WaitingNumbersProcessor = new BackgroundWorker();
      m_WaitingNumbersProcessor.DoWork += new DoWorkEventHandler(m_WaitingNumbersProcessor_DoWork);
      m_WaitingNumbersProcessor.RunWorkerAsync();
    }

    private void m_WaitingNumbersProcessor_DoWork(object sender, DoWorkEventArgs e)
    {
      while (true)
      {
        int waitingCount = 0;
        lock (m_WaitingNumbers)
        {
          waitingCount = m_WaitingNumbers.Count;
        }
        if (waitingCount == 0)
        {
          Thread.Sleep(10000);
          continue;
        }
        while (true)
        {
           WeakReference numberReference = null;
           lock (m_WaitingNumbers)
           {
             if (m_WaitingNumbers.Count == 0)
               break;
             numberReference = m_WaitingNumbers.First.Value;
             m_WaitingNumbers.RemoveFirst();
            }
            
            if (!numberReference.IsAlive)
              continue;
            FactoringBigNumber number = numberReference.Target as FactoringBigNumber;
            if (!AllocateFactoringThread(number,false))
              break;
         }
        }
      }

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
      FactoringBigNumber number = e.Argument as FactoringBigNumber;
      bool done = number.FullFactorization();
      if (!done)
      {
        lock (m_WaitingNumbers)
        {
          WeakReference tempReference = new WeakReference(number,false);
          tempReference.Target = number;
          m_WaitingNumbers.AddLast(tempReference);
        }
      }
      lock (m_Threads)
      {
        BackgroundWorker worker = sender as BackgroundWorker;
        m_Threads[worker] = true;
        m_BusyThreadCount--;
        return;
      }
    }

    internal void AllocateFactoringThread(FactoringBigNumber number)
    {
      AllocateFactoringThread(number, true);
    }

    private bool AllocateFactoringThread(FactoringBigNumber number, bool addWaiting)
    {
      lock (m_Threads)
      {
        foreach (BackgroundWorker worker in m_Threads.Keys)
        {
          if (m_Threads[worker] && !worker.IsBusy)
          {
            m_Threads[worker] = false;
            m_BusyThreadCount++;
            worker.RunWorkerAsync(number);
            return true;
          }
        }
      }
      if (addWaiting)
      {
        lock (m_WaitingNumbers)
        {
          WeakReference tempReference = new WeakReference(number, false);
          tempReference.Target = number;
          m_WaitingNumbers.AddLast(tempReference);
        }
      }
      return false;
    }
    #endregion

    private FactoringManager()
    {
      InitializePrimesCalculation();
      InitializeThreadPool();
    }

    #region Public Prime Calculation Functions
    public ForewardSkipListNode<FastBigNumber> GetPrimesFirst()
    {
      return m_Data.Primes.First;
    }

    public List<FastBigNumber> GetKnownPrimes()
    {
      List<FastBigNumber> result = new List<FastBigNumber>();
      ForewardSkipListNode<FastBigNumber> node = GetPrimesFirst();
      while (node != null)
      {
        result.Add(node.Data);
        node++;
      }
      return result;
    }

    /// <summary>
    /// This function runs the actual calculation on a diffrent thread
    /// </summary>
    /// <param name="number"></param>
    private void DoCalculation()
    {
      FastBigNumber number;
      lock (m_CalculationLock)
      {
         number = new FastBigNumber(m_CalculationLimit);
      }

      DateTime prevTime = DateTime.Now;
      DateTime nextTime = DateTime.Now;
      while (true)
      {
        while (m_Data.LastNumberCalculated < number)
        {
          m_Data.LastNumberCalculated = m_Data.LastNumberCalculated + 2;
          if (IsPrime(m_Data.LastNumberCalculated))
          {
            AddToFile(m_PrimesFilename, m_Data.LastNumberCalculated);
            m_Data.Primes.Add(m_Data.LastNumberCalculated);
            //          Debug.WriteLine("Found new prime " + m_Data.LastNumberCalculated);
            nextTime = DateTime.Now;
            TimeSpan span = nextTime - prevTime;
            if (span.TotalSeconds > 120)
            {
              prevTime = DateTime.Now;
              nextTime = DateTime.Now;
              CreateBackup(m_PrimesFilename);
            }
          }
        }
        CreateBackup(m_PrimesFilename);
        lock (m_CalculationLock)
        {
          if (m_CalculationLimit <= number)
            break;
          else
            number = new FastBigNumber(m_CalculationLimit);
        }
      }
    }

    /// <summary>
    /// Procedes with the calculation of next primes until number is reached
    /// </summary>
    /// <param name="number">The number to reach</param>
    public void ContinueCalculation(FastBigNumber number)
    {
      lock (m_CalculationLock)
      {
        if (Active)
        {
          lock (m_CalculationLock)
          {
            if (m_CalculationLimit < number)
              m_CalculationLimit = new FastBigNumber(number);
            return;
          }
        }
        if (number < m_Data.LastNumberCalculated)
          return;
        m_CalculationLimit = new FastBigNumber(number);
        m_CalculationThread.Start();
      }
    }

    /// <summary>
    /// Checks if the number is prime. This implementation is based on the naive approach that uses all previous primes to test for primality.
    /// </summary>
    /// <param name="number"></param>
    /// <returns></returns>
    private bool IsPrime(FastBigNumber number)
    {
      ForewardSkipListNode<FastBigNumber> node = m_Data.Primes.First;
      FastBigNumber root = BigNumberMath.SquareRoot<FastBigNumber>(number);
      while (node != null && node.Data <= root)
      {     
        FastBigNumber current = node.Data;
        FastBigNumber mod = number % current;
        if (mod.IsZero)
          return false;
        node++;
      }
      return true;
    }

    /// <summary>
    /// Returns true if the generator is in a middle of a calculation and false otherwise
    /// </summary>
    public bool Active
    {
      get
      {
        return m_CalculationThread.IsAlive;
      }
    }
    #endregion
  }
}
